Skip to content

Conversation

haslinghuis
Copy link
Member

@haslinghuis haslinghuis commented Jun 10, 2025

  • use fixed timeout to reduce buffer overflow issues
  • fix yarn test
  • update yarn test to use latest firmware
  • adds msp monitoring queue monitoring tool for testing

image

image

image

Summary by CodeRabbit

  • New Features

    • Interactive MSP Debug Dashboard, real-time queue monitor, stress-test framework, browser test runner, and lazy-loaded debug tools with console quick-start and global controls.
  • Bug Fixes

    • Safer configuration retrieval to avoid null errors.
    • Browser compatibility now treats test environments as compatible.
  • Refactor

    • Message sending simplified: fixed TIMEOUT, unified retry/timeout behavior, added timing measurements and long-run warnings, simplified duplicate and connection checks.
  • Documentation

    • Added comprehensive MSP debug tools README.
  • Tests

    • Updated tests to new API version.

@haslinghuis haslinghuis added this to the 11.0 milestone Jun 10, 2025
@haslinghuis haslinghuis self-assigned this Jun 10, 2025
Copy link
Contributor

coderabbitai bot commented Jun 10, 2025

Walkthrough

Replaces MSP adaptive timeout/flags with a fixed TIMEOUT and simplifies send_message; adds top-level dynamic import bootstrapping MSP debug tools; treats test environments as compatible in browser checks; bumps one test API version; and introduces a new MSP debug suite (README, barrel, dashboard, queue monitor, stress tester, test runner, tools).

Changes

Cohort / File(s) Change Summary
MSP timeout & send logic
src/js/msp.js
Removed adaptive timeout constants/variable and messageIsJumboFrame; added fixed TIMEOUT = 1000; simplified connection and duplicate checks; request objects gain start timestamp and timer; timers use TIMEOUT; dynamic timeout adjustment/retry logic removed; added long-execution warning and timer cleanup; refined send callback conditions.
Main entry & debug bootstrap
src/js/main.js
Added top-level dynamic import of ./msp/debug/msp_debug_tools.js with console banner and failure warning; made first-run config retrieval null-safe via nullish fallback; minor formatting tweaks.
Browser compatibility
src/js/utils/checkBrowserCompatibility.js
Added isTestEnvironment detection (Node/Jest) and treat test environments as compatible; no signature changes.
Tests: API version bump
test/js/msp/MSPHelper.test.js
Updated API version references from API_VERSION_1_46 to API_VERSION_1_47 only.
Debug tooling barrel + bootstrap
src/js/msp/debug/index.js
src/js/msp/debug/msp_debug_tools.js
New barrel export re-exporting debug utilities; msp_debug_tools.js imports debug modules, prints a console banner, and auto-starts basic monitoring on dev hosts.
Debug README
src/js/msp/debug/MSP_DEBUG_README.md
New comprehensive README documenting MSP Debug Tools, APIs, usage, tests, scenarios, dashboard interactions, troubleshooting, and contribution notes.
Debug dashboard UI & API
src/js/msp/debug/msp_debug_dashboard.js
Added MSPDebugDashboard class and exported singleton mspDebugDashboard; implements DOM/CSS UI, canvas live metrics, pause-on-hover behavior, event delegation, keyboard shortcut, and exposes window.MSPDebug API surface.
Queue monitor instrumentation
src/js/msp/debug/msp_queue_monitor.js
Added MSPQueueMonitor class and lazy mspQueueMonitor proxy; instruments MSP methods to collect metrics, alerts, thresholds, listener notifications, analysis, reporting, and restore/destroy capabilities.
Stress test harness
src/js/msp/debug/msp_stress_test.js
Added MSPStressTest class and lazy mspStressTest wrapper implementing nine test scenarios, suite runner, integration with monitor, reporting/grading, and public APIs to run tests and generate reports.
Browser test runner
src/js/msp/debug/msp_test_runner.js
Added MSPTestRunner exposing quick monitoring, runTest/runFullSuite, analyze/report, stress scenarios, quickHealthCheck, and global window.MSPTestRunner.

Sequence Diagram(s)

MSP send_message with fixed TIMEOUT

sequenceDiagram
    participant Caller
    participant MSP
    participant Serial
    participant Timer

    Caller->>MSP: send_message(code, data, callback...)
    MSP->>MSP: if (!serial.connected) -> callback_msp early return
    MSP->>MSP: check duplicate via callbacks.some(...)
    alt send permitted (no duplicate or data present)
        MSP->>MSP: create request {code, callback, start: performance.now(), ...}
        MSP->>Timer: setTimeout(..., TIMEOUT)
        MSP->>Serial: serial.send(bufferOut, cb)
        Serial-->>MSP: send callback -> set obj.stop, compute executionTime, call callback_sent if bytes match
        Timer-->>MSP: timeout fires -> if executionTime>5000ms log long-exec warning, clear timer, invoke timeout handling
    else duplicate and no data
        MSP->>Caller: skip send, resolve via callback_msp
    end
Loading

Dynamic loading of MSP Debug Tools (high-level)

sequenceDiagram
    participant App as Main App
    participant Import as Dynamic Import
    participant Tools as MSP Debug Tools
    participant Dashboard as mspDebugDashboard
    participant Monitor as mspQueueMonitor
    participant Stress as mspStressTest

    App->>Import: import('./src/js/msp/debug/msp_debug_tools.js')
    Import-->>Tools: load modules, print console banner
    alt dev host (localhost/127.0.0.1)
        Tools->>Monitor: dynamic import and startMonitoring(2000)
        Tools->>App: console guidance messages
    end
    Tools->>Dashboard: initialize UI (on demand)
    Dashboard->>Monitor: subscribe for live updates
    Dashboard->>Stress: trigger stress tests
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🔭 Outside diff range comments (1)
src/js/msp.js (1)

402-413: ⚠️ Potential issue

_validateSendMessage ignores doCallbackOnError flag

Error-side invocation of callback_msp is unconditional. That breaks the original contract implied by the doCallbackOnError parameter and can trigger “double callbacks” or resolve a Promise that expected an actual response.

-_validateSendMessage(code, callback_msp) {
+_validateSendMessage(code, callback_msp, doCallbackOnError = false) {-    if (callback_msp) {
+    if (doCallbackOnError && callback_msp) {
         callback_msp();
     }

You’ll have to forward the flag from send_message.

🧹 Nitpick comments (2)
src/js/msp.js (2)

423-431: Unused field – callbackOnError is never referenced

_createRequestObject stores callbackOnError, but no subsequent code reads it after the refactor. Either remove the property or reinstate the error-handling path.

Eliminating dead fields simplifies debugging and reduces cognitive load.


457-467: Aggressive timeout decay may under-shoot MIN_TIMEOUT

this.timeout-- happens on every send, regardless of frequency, and can push the value below MIN_TIMEOUT when timeout has already been clamped lower elsewhere.

-if (this.timeout > this.MIN_TIMEOUT) {
-    this.timeout--;
-}
+this.timeout = Math.max(this.MIN_TIMEOUT, this.timeout - 1);

A simple guard prevents negative or sub-minimum timeouts.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dafacbd and 8c40cfe.

📒 Files selected for processing (1)
  • src/js/msp.js (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/js/msp.js (2)
src/js/serial.js (2)
  • serial (364-364)
  • serial (364-364)
src/js/gui.js (1)
  • GUI (484-484)
🔇 Additional comments (1)
src/js/msp.js (1)

415-418: Duplicate detection is too coarse

_isDuplicateRequest only checks equality of code; it ignores the payload (data).
Two requests with the same MSP code but different parameters will be treated as duplicates and the second payload may never be transmitted.

Consider comparing a (code, dataHash) tuple or always sending when dataLength > 0 (even if duplicate).

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9a05f3a and 3dd8ae1.

📒 Files selected for processing (1)
  • src/js/msp.js (1 hunks)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
src/js/msp.js (3)

390-398: Unused allocation for duplicates

requestObj is instantiated even when the call is considered a duplicate and then discarded. This happens on every duplicate invocation and can become a noticeable GC hot-spot under heavy traffic.

Move the allocation inside the if (!isDuplicateRequest) block so it is only created when needed.


445-456: Timer remains referenced after giving up – clear it to aid GC

When MAX_RETRIES is exceeded the function bails out but does not clearTimeout(requestObj.timer);.
While the timer has already fired, keeping the handle prevents some JS engines from reclaiming it promptly.

 if (requestObj.attempts >= this.MAX_RETRIES) {
     console.error(`MSP: Request ${requestObj.code} exceeded max retries (${this.MAX_RETRIES}), giving up`);
-    // Remove from callbacks to prevent memory leak
+    clearTimeout(requestObj.timer);          // 💡 explicit cleanup
+    // Remove from callbacks to prevent memory leak
     this._removeRequestFromCallbacks(requestObj);

411-413: Adaptive back-off step might starve high-latency links

this.timeout is decreased by a fixed 5 ms on every successful send. On slow links (e.g. Bluetooth) this can quickly shrink below the real RTT, leading to unnecessary retries. Consider decaying by a percentage (e.g. timeout *= 0.9) or pinning a lower bound based on recent RTT measurements instead of a constant 5 ms.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 031b3ec and 072b49c.

📒 Files selected for processing (1)
  • src/js/msp.js (3 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/js/msp.js (3)
src/js/serial.js (2)
  • serial (364-364)
  • serial (364-364)
src/js/data_storage.js (1)
  • CONFIGURATOR (7-40)
src/js/gui.js (1)
  • GUI (484-484)

src/js/msp.js Outdated
});
}, this.timeout);
// Track only the first outstanding request for a given code
if (!isDuplicateRequest) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure if this is the reason for missing events? e.g. motor slider getting "stuck" on old values even if the slider got pulled all the way down ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah - see next commit - not sure if enough - or different from master - at least seems more stable - no longer locking up on SPBEF7V3 for the moment as it does on master.

@haslinghuis haslinghuis force-pushed the improve-msp branch 3 times, most recently from eb77f13 to 4fcad77 Compare June 10, 2025 23:40
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/js/msp.js (1)

399-420: ⚠️ Potential issue

Duplicate requests silently drop callbacks

When isDuplicateRequest is true, the new callback_msp is not stored anywhere, so callers issuing concurrent identical requests will never be notified.
This regresses previous behaviour and breaks APIs that rely on per-call completion.

Quick remedy: attach additional callbacks to the existing request object instead of discarding them.

-const isDuplicateRequest = this.callbacks.some((instance) => instance.requestKey === requestKey);
+const existingReq = this.callbacks.find((i) => i.requestKey === requestKey);
+const isDuplicateRequest = Boolean(existingReq);-if (!isDuplicateRequest) {
+if (!isDuplicateRequest) {
     this._setupTimeout(requestObj, bufferOut);
     this.callbacks.push(requestObj);
+} else if (callback_msp) {
+    // fan-out: queue extra callback(s) on the first request
+    (existingReq.extraCallbacks ??= []).push(callback_msp);
 }

And, when the response arrives, invoke extraCallbacks as well.

🧹 Nitpick comments (4)
src/js/msp.js (4)

376-389: Hash function may collide & yield negative keys

_createRequestKey uses a simple 32-bit rolling hash that can:

  1. Produce the same hash for different payloads (higher collision risk on large traffic).
  2. Return negative numbers, generating keys like 123:-842150451 which are harder to eyeball/debug.

Consider switching to a safer deterministic scheme, e.g. CRC-32 or base64 of the first 4 bytes plus length, to lower collision odds and keep keys human-readable.


421-428: Inefficient duplicate scan — O(N) each call

this.callbacks.some() does a linear search every time send_message is invoked.
With MAX_QUEUE_SIZE already tracked, switching to a Map<requestKey, requestObj> would give O(1) look-ups and simplify removal logic.


433-438: Timer handle lost if _setupTimeout is called twice

_setupTimeout overwrites requestObj.timer without clearing a possibly existing one.
Although current flow prevents re-entrance, guarding against future changes is cheap:

+if (requestObj.timer) {
+    clearTimeout(requestObj.timer);
+}
 requestObj.timer = setTimeout(() => {
     this._handleTimeout(requestObj, bufferOut);
 }, this.TIMEOUT);

439-472: Retry path never resets start — metrics become meaningless

requestObj.start is set once in send_message but never updated on retries, so any later duration calculations accumulate total wall-time, not per-attempt latency.

Either reset start inside _handleTimeout before re-arming the timer or drop it altogether if no longer used.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 072b49c and 20f3c6a.

📒 Files selected for processing (1)
  • src/js/msp.js (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/js/msp.js (2)
src/js/serial.js (2)
  • serial (364-364)
  • serial (364-364)
src/js/data_storage.js (1)
  • CONFIGURATOR (7-40)
🔇 Additional comments (1)
src/js/msp.js (1)

69-73:

✅ Verification successful

Confirm removal of legacy this.timeout references

You have introduced a fixed TIMEOUT constant but removed the old adaptive this.timeout.
Please grep the rest of the file / project for stale this.timeout usages to avoid undefined-property bugs at runtime.


🏁 Script executed:

#!/bin/bash
# Find any remaining references to the old timeout field
rg --line-number $'this\\.timeout'

Length of output: 665


No stale this.timeout references found

A full‐repo search for this.timeout only returns occurrences of this.timeout_array in src/js/gui.js, not the legacy this.timeout property. There are no remaining direct references to the removed adaptive timeout field—this concern can be closed.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/js/msp.js (1)

482-491: _removeRequestFromCallbacks leaves Map/key tracking unresolved

If you adopt the Map optimisation above, ensure this method also deletes the requestKey entry to keep the structures in sync.

🧹 Nitpick comments (4)
src/js/msp.js (4)

69-73: Expose retry parameters instead of hard-coding them

MAX_RETRIES, MAX_QUEUE_SIZE, and TIMEOUT are baked into the module, making it impossible to tune behaviour from the outside (e.g. per-board, per-environment, unit-tests). Consider reading them from CONFIGURATOR, a constructor argument, or at least exporting setters.


399-420: Duplicate check performs O(n) scans on every call

this.callbacks.some(...) linearly scans the queue. Under heavy traffic (e.g. telemetry floods) the cost grows quadratically.

Two easy wins:

  1. Keep a Map<requestKey, requestObj> to get O(1) look-ups and removals.
  2. Build bufferOut after the duplicate check to avoid unnecessary encoding work when the request is dropped.

433-438: Timer double-cleared – harmless but needless

_handleTimeout calls clearTimeout(requestObj.timer) (l.464) and _removeRequestFromCallbacks does the same. The second call is redundant and can be omitted to save a micro-op.


439-480: Constant timeout may hammer dead links

Retries fire every fixed TIMEOUT ms. On an unplugged device this means 10 exact re-transmits per second, wasting bandwidth and log space.
An exponential back-off (e.g. TIMEOUT * 2 ** attempts) curbs network noise and still converges quickly when the link is restored.

🛑 Comments failed to post (1)
src/js/msp.js (1)

376-389: 🛠️ Refactor suggestion

_createRequestKey collision risk & signed-shift bug

The DJB-style hash on a Uint8Array will inevitably collide for different payloads and, because & 0xffffffff yields a signed 32-bit value, the string key can even become negative.
A wrong “duplicate” classification silently drops tracking / retries.

-        let hash = 0;
-        for (const byte of data) {
-            hash = ((hash << 5) - hash + byte) & 0xffffffff;
-        }
-        return `${code}:${hash}`;
+        // Use a stable unsigned hash (FNV-1a) to minimise collisions
+        let hash = 2166136261 >>> 0;
+        for (const byte of data) {
+            hash ^= byte;
+            hash = Math.imul(hash, 16777619) >>> 0;
+        }
+        return `${code}:${hash.toString(16)}`;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    // Helper function to create a unique key for request identification
    _createRequestKey(code, data) {
        if (!data || data.length === 0) {
            return `${code}:empty`;
        }

        // Use a stable unsigned hash (FNV-1a) to minimise collisions
        let hash = 2166136261 >>> 0;
        for (const byte of data) {
            hash ^= byte;
            hash = Math.imul(hash, 16777619) >>> 0;
        }

        return `${code}:${hash.toString(16)}`;
    },
🤖 Prompt for AI Agents
In src/js/msp.js around lines 376 to 389, the _createRequestKey function uses a
DJB-style hash that can produce collisions and negative values due to signed
32-bit shifts. To fix this, replace the hash calculation with a more robust
hashing method that avoids collisions and ensures the hash is always unsigned
and positive. For example, use a standard hashing library or implement a
non-signed hash function that produces a unique, non-negative key string for the
given data.

src/js/msp.js Outdated
}
// Create unique key combining code and data
const requestKey = this._createRequestKey(code, data);
const isDuplicateRequest = this.callbacks.some((instance) => instance.requestKey === requestKey);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not return here if a dupe is detected ? saves your the conditions below. looks like the code is not following "one return per function" convention anyways.

Copy link

@AJ92 AJ92 Jun 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and, why not "rate-limit" the events from sliders at the source ? i bet there are different event types that need different handling anyways.... like those motor sliders, where probably first and last event is very important, but all events in between can be lost without any noticeable effect for the user, but then there are events that cannot be lost, like transmission of cli stuff ?

src/js/msp.js Outdated
Comment on lines 417 to 423
this._setupTimeout(requestObj, bufferOut);
this.callbacks.push(requestObj);
}

this.callbacks.push(obj);

// always send messages with data payload (even when there is a message already in the queue)
if (data || !requestExists) {
if (this.timeout > this.MIN_TIMEOUT) {
this.timeout--;
}

// Send message if it has data or is a new request
if (data || !isDuplicateRequest) {
serial.send(bufferOut, (sendInfo) => {
Copy link

@AJ92 AJ92 Jun 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(note the line range if this comment)

i would probably start the timeout and call send only if the queue is empty, or if the previous "send" succeeded?

otherwise the buffers of the serial protocols are immediately filled and can overflow

src/js/msp.js Outdated
);

// Check if max retries exceeded OR queue is too large
if (requestObj.attempts >= this.MAX_RETRIES || this.callbacks.length > this.MAX_QUEUE_SIZE) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i would perform the queue size check prior to insertion of the objects, in the send_message function

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
src/js/msp.js (4)

69-73: Freeze or namespace retry-config constants to prevent accidental mutation

Defining MAX_RETRIES, MAX_QUEUE_SIZE and TIMEOUT directly on the mutable MSP object means any consumer can overwrite them at runtime:

MSP.TIMEOUT = 1;   // 💥

If you really want them tweakable, expose an explicit setter.
Otherwise, lock them down:

-    MAX_RETRIES: 10,
-    MAX_QUEUE_SIZE: 50,
-    TIMEOUT: 1000,
+    MAX_RETRIES: 10,
+    MAX_QUEUE_SIZE: 50,
+    TIMEOUT: 1000,
 }
+
+Object.freeze(MSP);            // or freeze only MSP.retryConfig sub-object

376-389: Hashing scheme is fragile & collision-prone

_createRequestKey builds a 32-bit additive hash; for large or similar payloads collisions are easy, producing false “duplicates”.
Additionally hash may end up negative, so keys like 42:-123 and 42:327375 look different but are produced from different sessions of the same data length.

Consider:

  • Use a stable digest (e.g. DJB2, MurmurHash) or crypto.subtle.digest('SHA-1', data) when available.
  • Fall back to btoa(String.fromCharCode(...data)) for short payloads.
  • At minimum, >>> 0 to keep the value unsigned.

421-428: Guard callback_sent with a functional check

promise() passes false, so callback_sent() may be a boolean.
A trivial typeof guard avoids accidental false is not a function mistakes if the signature changes upstream.

-if (sendInfo.bytesSent === bufferOut.byteLength && callback_sent) {
+if (sendInfo.bytesSent === bufferOut.byteLength && typeof callback_sent === "function") {
     callback_sent();
 }

479-489: O(N²) removal can hurt with large queues

indexOf + splice is fine for the 50-item cap, but if the limit is raised later it becomes quadratic.
Storing callbacks in a Map<requestKey, requestObj> would give O(1) insert/remove/lookup, simplify duplicate handling, and eliminate linear scans elsewhere.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8b03408 and debfc36.

📒 Files selected for processing (1)
  • src/js/msp.js (2 hunks)
🔇 Additional comments (2)
src/js/msp.js (2)

433-438: Timeout set-up looks good

_setupTimeout is concise and centralises timer arming – nice.


439-477: Retry handler: no back-off & relies on sendInfo.bytesSent

  1. A fixed 1 s retry interval can hammer sluggish links. Consider exponential back-off or TIMEOUT * (1 + attempts) to avoid congestion.
  2. If the serial driver does not populate bytesSent, sendInfo.bytesSent === bufferOut.byteLength evaluates false ⇒ the request aborts immediately. Validate field presence or fall back to truthiness.
- if (sendInfo.bytesSent === bufferOut.byteLength) {
+ if (!sendInfo || sendInfo.bytesSent === undefined || sendInfo.bytesSent === bufferOut.byteLength) {
  1. You re-assign requestObj.timer but never clear it inside _handleTimeout.
    Although the fired timer auto-invalidates, explicitly clearTimeout(requestObj.timer) before re-arming removes any doubt.

@nerdCopter
Copy link
Member

checking the coderrabbit "nitpicks"? i'm finding them useful outside of this project.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/js/msp.js (1)

397-416: Timeout handler: wrong variable in log, single re-send, no retry cap, and no cleanup on terminal failure.

Issues:

  • Uses this.timeout (removed) instead of this.TIMEOUT in the warning.
  • Only a single re-send occurs; if that packet is also lost, the request can hang indefinitely.
  • No retry cap; no error callback on terminal failure; stale entries remain in callbacks.

Fix all in place:

         if (!requestExists) {
             obj.timer = setTimeout(() => {
                 console.warn(
-                    `MSP: data request timed-out: ${code} ID: ${serial.connectionId} TAB: ${GUI.active_tab} TIMEOUT: ${
-                        this.timeout
-                    } QUEUE: ${this.callbacks.length} (${this.callbacks.map((e) => e.code)})`,
+                    `MSP: data request timed-out: ${code} ID: ${serial.connectionId} TAB: ${GUI.active_tab} TIMEOUT: ${this.TIMEOUT} QUEUE: ${this.callbacks.length} (${this.callbacks.map((e) => e.code)})`,
                 );
-                serial.send(bufferOut, (_sendInfo) => {
+                // Increment attempts and enforce retry cap
+                obj.attempts = (obj.attempts || 0) + 1;
+                const MAX_RETRIES = 3;
+                if (obj.attempts > MAX_RETRIES) {
+                    // Give up: remove from queue and notify caller (if desired)
+                    clearTimeout(obj.timer);
+                    this._removeRequestFromCallbacks?.(obj);
+                    if (obj.callbackOnError && obj.callback) obj.callback({ error: "MSP timeout", code });
+                    return;
+                }
+
+                serial.send(bufferOut, (_sendInfo) => {
                     obj.stop = performance.now();
                     const executionTime = Math.round(obj.stop - obj.start);
                     // We should probably give up connection if the request takes too long ?
                     if (executionTime > 5000) {
                         console.warn(
                             `MSP: data request took too long: ${code} ID: ${serial.connectionId} TAB: ${GUI.active_tab} EXECUTION TIME: ${executionTime}ms`,
                         );
                     }
-
-                    clearTimeout(obj.timer); // prevent leaks
+                    // Re-arm timeout for the next retry window
+                    clearTimeout(obj.timer);
+                    obj.timer = setTimeout(() => {
+                        // Recurse into the same handler
+                        // NOTE: we rely on the outer closure's variables (obj, bufferOut)
+                        // to drive further retries or terminal failure.
+                        console.warn(`MSP: retrying (${obj.attempts}/${MAX_RETRIES}) for code ${code}`);
+                        // Trigger the same resend path
+                        serial.send(bufferOut, () => {});
+                    }, this.TIMEOUT);
                 });
             }, this.TIMEOUT);
         }

If you prefer a cleaner shape, introduce a dedicated _handleTimeout(obj, bufferOut) and centralize removal logic in _removeRequestFromCallbacks (see below).

♻️ Duplicate comments (2)
src/js/msp.js (2)

385-385: Duplicate detection by code only is too coarse; conflates distinct payloads and skews timers.

Using only instance.code misclassifies concurrent requests with the same code but different payloads. It also means only the first one gets a timer; later ones will sit untracked if the reply never comes.

Apply this minimal diff to defer duplicate detection until bufferOut exists and compare code + payload bytes:

-        const requestExists = this.callbacks.some((instance) => instance.code === code);
+        // Defer precise duplicate detection until after bufferOut is created
+        let requestExists = false;
+        let existing = null;

…and immediately after bufferOut is constructed (below Line 387), insert:

+        // Precise duplicate detection: same code AND identical payload
+        const outView = new Uint8Array(bufferOut);
+        existing = this.callbacks.find((i) => {
+            if (i.code !== code) return false;
+            const iv = new Uint8Array(i.requestBuffer);
+            if (iv.byteLength !== outView.byteLength) return false;
+            for (let k = 0; k < iv.byteLength; k++) {
+                if (iv[k] !== outView[k]) return false;
+            }
+            return true;
+        });
+        requestExists = Boolean(existing);

419-419: Chain duplicate MSP request callbacks instead of pushing new ones

To avoid queuing multiple identical MSP requests (which leads to redundant serial sends and un-timed callbacks), refactor send_message so that duplicate calls attach their callbacks to the first outstanding request, and update the response handler to fire those chained callbacks.

Key locations:

  • src/js/msp.js (send_message, lines ~383–419): detect an existing request and only push a new entry when none exists; otherwise, append the new callback_msp to existing.chained.
  • src/js/msp/MSPHelper.js (around line 1768): after firing the primary callback, invoke any chained callbacks before removing the entry.

Suggested diff for src/js/msp.js:

--- a/src/js/msp.js
+++ b/src/js/msp.js
@@ send_message(code, data, callback_sent, callback_msp, doCallbackOnError) {
-        const requestExists = this.callbacks.some((instance) => instance.code === code);
+        // Check for an in-flight request with the same code
+        const existing = this.callbacks.find((instance) => instance.code === code);
+        const requestExists = !!existing;
 
         const bufferOut = code <= 254
             ? this.encode_message_v1(code, data)
             : this.encode_message_v2(code, data);
 
         const obj = {
             code,
             requestBuffer: bufferOut,
             callback: callback_msp,
             callbackOnError: doCallbackOnError,
             start: performance.now(),
         };
 
-        if (!requestExists) {
-            obj.timer = setTimeout(...);
-        }
-
-        this.callbacks.push(obj);
+        if (!requestExists) {
+            // schedule timeout/retry only once
+            obj.timer = setTimeout(/* existing timeout handler */);
+            this.callbacks.push(obj);
+        } else if (callback_msp) {
+            // chain this callback onto the existing pending request
+            (existing.chained || (existing.chained = [])).push(callback_msp);
+        }
 
         // always send when there's payload or first request
         if (data || !requestExists) {
             serial.send(bufferOut, (sendInfo) => {
                 if (sendInfo.bytesSent === bufferOut.byteLength && callback_sent) {
                     callback_sent();
                 }
             });
         }

Suggested diff for src/js/msp/MSPHelper.js:

--- a/src/js/msp/MSPHelper.js
+++ b/src/js/msp/MSPHelper.js
@@ // inside the loop handling an MSP response (around line 1768)
             // fire primary callback
             if (!crcError || callbackOnError) {
-                if (callback) {
-                    callback({ command: code, data, length: data.byteLength, crcError });
-                }
+                if (callback) {
+                    callback({ command: code, data, length: data.byteLength, crcError });
+                }
+                // fire any chained callbacks for duplicate requests
+                const chained = dataHandler.callbacks[i].chained;
+                if (chained) {
+                    chained.forEach((cb) =>
+                        cb({ command: code, data, length: data.byteLength, crcError })
+                    );
+                }
             }

Once these changes are in place, the primary request will handle both timeout/retries and cleanup, while all additional callers simply attach their callback to the same in-flight request.

🧹 Nitpick comments (18)
src/js/msp.js (5)

60-60: Fixed TIMEOUT constant: good step; consider making it configurable per-transport/test.

Hard-coding 1000ms is fine as a sane default. Consider allowing overrides via CONFIGURATOR or an optional param to send_message for test rigs and slow links.


378-383: Early-return invokes callback_msp() without error context.

Calling callback_msp() with no args conflates “not eligible to send” with “empty payload response.” Pass a sentinel (e.g., null or an Error-like object) so callers can discriminate.

-            if (callback_msp) {
-                callback_msp();
-            }
+            if (callback_msp) {
+                callback_msp({ error: "MSP send skipped: disconnected or virtual mode", code });
+            }

278-282: Leftover property set: messageIsJumboFrame is referenced but no longer tracked.

This looks like a remnant. It’s not defined in MSP anymore and resetting it every dispatch serves no purpose.

-        this.messageIsJumboFrame = false;

If you still need it, reintroduce a declared state property near Line 45.


67-67: Typo in comment (“charactor”).

-    cli_buffer: [], // buffer for CLI charactor output
+    cli_buffer: [], // buffer for CLI character output

1-465: Optional: add _removeRequestFromCallbacks helper to centralize timer cleanup.

Centralizes clearTimeout + splice to avoid dangling timers. Call it from the retry-cap branch and the normal completion path.

// Add inside MSP object (near callbacks_cleanup)
_removeRequestFromCallbacks(requestObj) {
    const idx = this.callbacks.indexOf(requestObj);
    if (idx > -1) {
        try { clearTimeout(this.callbacks[idx].timer); } catch (e) {}
        this.callbacks.splice(idx, 1);
    }
},
src/js/msp/debug/MSP_DEBUG_README.md (2)

317-330: Add a language hint to the fenced “File Structure” block to satisfy MD040.

-```
+```text
 src/js/msp/
 ├── MSPCodes.js
 ├── MSPConnector.js  
 ├── MSPHelper.js
 └── debug/
     ├── msp_queue_monitor.js     # Core monitoring functionality
     ├── msp_stress_test.js       # Stress testing framework
     ├── msp_debug_dashboard.js   # Visual dashboard UI
     ├── msp_test_runner.js       # Console command interface
     ├── msp_debug_tools.js       # Integration and auto-loading
     └── MSP_DEBUG_README.md      # Documentation

---

`1-365`: **Minor wording polish (optional).**

Static analysis flagged a lot of “There might be a mistake here” on headings because of emojis and bold markers. Content is clear; no action needed unless you want a perfectly clean linter run.

</blockquote></details>
<details>
<summary>src/js/msp/debug/index.js (1)</summary><blockquote>

`1-16`: **Barrel export looks good; watch for export name collisions.**

export * from './msp_debug_tools.js' will be a no-op if that file doesn’t export names (only side effects). Keep it if you plan to export from there later; otherwise you can drop it.

</blockquote></details>
<details>
<summary>src/js/msp/debug/msp_debug_tools.js (2)</summary><blockquote>

`36-52`: **Guard for non-browser/test environments and avoid double import.**

This file assumes window exists. In node/JSDOM tests that import it directly, window may be undefined. Also, you already import "./msp_queue_monitor.js" at the top; the dynamic import is redundant.

```diff
-if (window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1") {
+if (typeof window !== "undefined"
+    && window.location
+    && (window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1")) {
-    // Import the monitor and start it with minimal logging
-    import("./msp_queue_monitor.js").then(({ mspQueueMonitor }) => {
+    // Use the already-loaded export
+    import("./msp_queue_monitor.js").then(({ mspQueueMonitor }) => {
         mspQueueMonitor.addListener((status) => {
             // Only log alerts and significant events
             const alerts = Object.values(status.alerts).filter((a) => a);
             if (alerts.length > 0) {
                 console.warn("🚨 MSP Alert detected - check dashboard for details");
             }
         });
 
         mspQueueMonitor.startMonitoring(2000); // Monitor every 2 seconds
     });
 }

Optional: import { mspQueueMonitor } at the top instead of dynamic importing in dev.


12-34: Console banner in production (optional).

Consider gating the banner behind a dev flag to keep end-user consoles clean.

-console.log(`
+if (typeof window !== "undefined" && (window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1")) console.log(`
 …
 `);
src/js/msp/debug/msp_debug_dashboard.js (2)

675-699: Harden update path against missing MSP/mspQueueMonitor during early startup.

If the dashboard is shown before MSP is initialized, mspQueueMonitor.getStatus() can throw. Wrap in try/catch and bail gracefully.

-        status = status || mspQueueMonitor.getStatus();
+        try {
+            status = status || mspQueueMonitor.getStatus();
+        } catch (e) {
+            console.warn("MSP Debug Dashboard: status unavailable yet", e);
+            return;
+        }

794-806: Avoid repeated DOM writes due to type mismatch in updateElement.

textContent is string; value can be number. Coerce once to string for a stable equality check.

-        if (element && element.textContent !== value) {
-            element.textContent = value;
+        if (element) {
+            const newText = String(value);
+            if (element.textContent !== newText) {
+                element.textContent = newText;
+            }
         }
src/js/msp/debug/msp_queue_monitor.js (2)

447-456: Timeout-rate deduction uses a possibly stale/undefined metrics.timeoutRate; compute locally

_calculatePerformanceGrade() should not rely on startMonitoring/_collectMetrics() having run. Compute the rate from raw counters to avoid undefined reads.

Apply this diff:

-        // Deduct for high timeout rate
-        if (this.metrics.timeoutRate > 0.1) {
+        // Deduct for high timeout rate
+        const timeoutRate =
+            this.metrics.totalRequests > 0 ? this.metrics.timeouts / this.metrics.totalRequests : 0;
+        if (timeoutRate > 0.1) {
             score -= 30;
-        } else if (this.metrics.timeoutRate > 0.05) {
+        } else if (timeoutRate > 0.05) {
             score -= 15;
         }

249-263: Expose immutable, plain snapshots to listeners to prevent accidental mutation of internal state

getStatus() shallow-copies metrics/alerts, but the Maps inside remain live references. Consider returning fully plain data (as in the report fix) or at least new Map instances to avoid accidental listener mutations.

Happy to provide a small _snapshot() helper that returns a deep-plain snapshot for both getStatus() and generateReport().

src/js/msp/debug/msp_test_runner.js (3)

43-53: Make periodic logging deterministic; avoid modulo-time race

if (Date.now() % 10000 < 500) can log 0–2 times depending on timer jitter. Track last-log time instead.

Apply this diff:

-            // Log every 10 seconds if monitoring
-            if (Date.now() % 10000 < 500) {
+            // Log every ~10 seconds if monitoring (deterministic)
+            const now = Date.now();
+            if (!this._lastQuickLogTs || now - this._lastQuickLogTs >= 10_000) {
+                this._lastQuickLogTs = now;
                 console.log(
                     `📊 MSP Status: Queue=${status.currentQueueSize}/${status.maxQueueSize}, Requests=${status.metrics.totalRequests}, AvgTime=${Math.round(status.metrics.avgResponseTime)}ms`,
                 );
             }

And initialize the tracking field:

 export const MSPTestRunner = {
     // Store the listener function so it can be removed later
     _quickMonitorListener: null,
+    _lastQuickLogTs: 0,

191-205: Preserve Map metrics in JSON download using a custom replacer

Even with the monitor fix, extra safety here ensures third-party objects serialize predictably.

Apply this diff:

-        const blob = new Blob([JSON.stringify(report, null, 2)], {
+        const replacer = (_key, value) => {
+            if (value instanceof Map) return Object.fromEntries(value);
+            return value;
+        };
+        const blob = new Blob([JSON.stringify(report, replacer, 2)], {
             type: "application/json",
         });

220-286: Quick health check: tighten timing consistency and clean up monitor even on early returns

Minor robustness: use performance.now() for latency consistency and ensure the monitor is stopped on all paths.

I can send a small patch converting the timing to performance.now() and using a single finally to stop monitoring.

src/js/msp/debug/msp_stress_test.js (1)

218-241: Duplicate detection relies on error message text; prefer a structured indicator

Counting duplicates via (err && err.message.includes("duplicate")) is brittle. If the MSP layer doesn’t expose a code, consider exposing a boolean/enum on rejection (e.g., { code: 'DUPLICATE' }) to avoid string matching.

If updating MSP isn’t feasible here, at least lower-case and guard to reduce flakiness:

-        const duplicateErrors = results.filter(
-            (r) => r.status === "rejected" || (r.value && r.value.error && r.value.error.includes("duplicate")),
-        ).length;
+        const duplicateErrors = results.filter((r) => {
+            if (r.status === "rejected") return true;
+            const msg = r.value && r.value.error;
+            return typeof msg === "string" && msg.toLowerCase().includes("duplicate");
+        }).length;
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 52e1aeb and 3433db1.

📒 Files selected for processing (11)
  • src/js/main.js (2 hunks)
  • src/js/msp.js (3 hunks)
  • src/js/msp/debug/MSP_DEBUG_README.md (1 hunks)
  • src/js/msp/debug/index.js (1 hunks)
  • src/js/msp/debug/msp_debug_dashboard.js (1 hunks)
  • src/js/msp/debug/msp_debug_tools.js (1 hunks)
  • src/js/msp/debug/msp_queue_monitor.js (1 hunks)
  • src/js/msp/debug/msp_stress_test.js (1 hunks)
  • src/js/msp/debug/msp_test_runner.js (1 hunks)
  • src/js/utils/checkBrowserCompatibility.js (1 hunks)
  • test/js/msp/MSPHelper.test.js (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • test/js/msp/MSPHelper.test.js
  • src/js/utils/checkBrowserCompatibility.js
  • src/js/main.js
🧰 Additional context used
🧬 Code graph analysis (6)
src/js/msp/debug/msp_debug_dashboard.js (2)
src/js/msp/debug/msp_queue_monitor.js (2)
  • mspQueueMonitor (622-692)
  • mspQueueMonitor (622-692)
src/js/msp/debug/msp_stress_test.js (2)
  • mspStressTest (681-756)
  • mspStressTest (681-756)
src/js/msp/debug/msp_debug_tools.js (2)
src/js/msp/debug/msp_queue_monitor.js (2)
  • mspQueueMonitor (622-692)
  • mspQueueMonitor (622-692)
src/js/msp/debug/msp_test_runner.js (4)
  • status (145-145)
  • status (245-245)
  • status (375-375)
  • a (197-197)
src/js/msp/debug/msp_queue_monitor.js (2)
src/js/msp/debug/msp_test_runner.js (6)
  • responseTime (241-241)
  • a (197-197)
  • analysis (166-166)
  • status (145-145)
  • status (245-245)
  • status (375-375)
src/js/msp/MSPHelper.js (2)
  • now (2343-2343)
  • callback (1771-1771)
src/js/msp/debug/msp_stress_test.js (2)
src/js/msp/debug/msp_queue_monitor.js (2)
  • mspQueueMonitor (622-692)
  • mspQueueMonitor (622-692)
src/js/msp/debug/msp_test_runner.js (6)
  • results (110-110)
  • startTime (107-107)
  • startTime (239-239)
  • result (84-84)
  • result (374-374)
  • duration (111-111)
src/js/msp/debug/msp_test_runner.js (3)
src/js/msp/debug/msp_queue_monitor.js (2)
  • mspQueueMonitor (622-692)
  • mspQueueMonitor (622-692)
src/js/msp/debug/msp_stress_test.js (2)
  • mspStressTest (681-756)
  • mspStressTest (681-756)
src/js/msp/debug/msp_debug_dashboard.js (2)
  • mspDebugDashboard (1153-1153)
  • mspDebugDashboard (1153-1153)
src/js/msp.js (3)
src/js/serial.js (2)
  • serial (319-319)
  • serial (319-319)
src/js/data_storage.js (1)
  • CONFIGURATOR (7-38)
src/js/gui.js (1)
  • GUI (483-483)
🪛 LanguageTool
src/js/msp/debug/MSP_DEBUG_README.md

[grammar] ~7-~7: There might be a mistake here.
Context: ...eatures 🔍 Real-time Queue Monitoring - Track queue size, response times, and su...

(QB_NEW_EN)


[grammar] ~8-~8: There might be a mistake here.
Context: ... size, response times, and success rates - Detect memory leaks and performance bott...

(QB_NEW_EN)


[grammar] ~10-~10: There might be a mistake here.
Context: ...ecks - Alert system for potential issues 🧪 Comprehensive Stress Testing - Qu...

(QB_NEW_EN)


[grammar] ~12-~12: There might be a mistake here.
Context: ...ssues 🧪 Comprehensive Stress Testing - Queue flooding tests - Timeout recovery ...

(QB_NEW_EN)


[grammar] ~13-~13: There might be a mistake here.
Context: ... Stress Testing** - Queue flooding tests - Timeout recovery validation - Memory lea...

(QB_NEW_EN)


[grammar] ~14-~14: There might be a mistake here.
Context: ...ding tests - Timeout recovery validation - Memory leak detection - Performance unde...

(QB_NEW_EN)


[grammar] ~15-~15: There might be a mistake here.
Context: ...overy validation - Memory leak detection - Performance under load testing 📊 **Vis...

(QB_NEW_EN)


[grammar] ~16-~16: There might be a mistake here.
Context: ...tection - Performance under load testing 📊 Visual Dashboard - Real-time metr...

(QB_NEW_EN)


[grammar] ~18-~18: There might be a mistake here.
Context: ...nder load testing 📊 Visual Dashboard - Real-time metrics display with smart upd...

(QB_NEW_EN)


[grammar] ~19-~19: There might be a mistake here.
Context: ...-time metrics display with smart updates - Live charts and graphs - Queue analysis ...

(QB_NEW_EN)


[grammar] ~20-~20: There might be a mistake here.
Context: ...h smart updates - Live charts and graphs - Queue analysis tools - Test result visua...

(QB_NEW_EN)


[grammar] ~21-~21: There might be a mistake here.
Context: ...charts and graphs - Queue analysis tools - Test result visualization - **Interactiv...

(QB_NEW_EN)


[grammar] ~22-~22: There might be a mistake here.
Context: ...alysis tools - Test result visualization - Interactive-friendly updates: Dashboar...

(QB_NEW_EN)


[grammar] ~23-~23: There might be a mistake here.
Context: ... pauses updates during user interactions - Clickable test results: Click on any t...

(QB_NEW_EN)


[grammar] ~24-~24: There might be a mistake here.
Context: ...any test result for detailed information - Visual pause indicators when updates are...

(QB_NEW_EN)


[grammar] ~25-~25: There might be a mistake here.
Context: ...se indicators when updates are suspended ⚡ Easy-to-use API - Console commands...

(QB_NEW_EN)


[grammar] ~27-~27: There might be a mistake here.
Context: ...dates are suspended ⚡ Easy-to-use API - Console commands for quick testing - Pro...

(QB_NEW_EN)


[grammar] ~28-~28: There might be a mistake here.
Context: ...I** - Console commands for quick testing - Programmable test scenarios - Detailed r...

(QB_NEW_EN)


[grammar] ~29-~29: There might be a mistake here.
Context: ...ck testing - Programmable test scenarios - Detailed reporting and export ## Quick ...

(QB_NEW_EN)


[grammar] ~85-~85: There might be a mistake here.
Context: ...s The MSP Debug Tools provide two APIs: - MSPDebug: Modern, simplified API (reco...

(QB_NEW_EN)


[grammar] ~86-~86: There might be a mistake here.
Context: ...**: Modern, simplified API (recommended) - MSPTestRunner: Legacy API with additio...

(QB_NEW_EN)


[grammar] ~93-~93: There might be a mistake here.
Context: ...ring Commands | Command | Description | |---------|-------------| | `MSPDebug.st...

(QB_NEW_EN)


[grammar] ~94-~94: There might be a mistake here.
Context: ... Description | |---------|-------------| | MSPDebug.startMonitoring() | Start m...

(QB_NEW_EN)


[grammar] ~95-~95: There might be a mistake here.
Context: ...| Start monitoring with console output | | MSPDebug.stopMonitoring() | Stop mon...

(QB_NEW_EN)


[grammar] ~96-~96: There might be a mistake here.
Context: ...ug.stopMonitoring()| Stop monitoring | |MSPDebug.getStatus()` | Get current M...

(QB_NEW_EN)


[grammar] ~97-~97: There might be a mistake here.
Context: ....getStatus()| Get current MSP status | |MSPDebug.monitor.getStatus()` | Get c...

(QB_NEW_EN)


[grammar] ~98-~98: There might be a mistake here.
Context: ...| Get current MSP status (alternative) | | MSPDebug.analyze() | Analyze current...

(QB_NEW_EN)


[grammar] ~103-~103: There might be a mistake here.
Context: ...ting Commands | Command | Description | |---------|-------------| | `MSPDebug.ru...

(QB_NEW_EN)


[grammar] ~104-~104: There might be a mistake here.
Context: ... Description | |---------|-------------| | MSPDebug.runTests() | Run stress tes...

(QB_NEW_EN)


[grammar] ~105-~105: There might be a mistake here.
Context: ...ug.runTests()| Run stress test suite | |MSPDebug.runFullSuite()` | Run comple...

(QB_NEW_EN)


[grammar] ~106-~106: There might be a mistake here.
Context: ...stress test suite with detailed output | | MSPDebug.runTest('test-name') | Run ...

(QB_NEW_EN)


[grammar] ~107-~107: There might be a mistake here.
Context: ...-name')| Run a specific test by name | |MSPDebug.quickHealthCheck()` | Run a ...

(QB_NEW_EN)


[grammar] ~108-~108: There might be a mistake here.
Context: ...heck()| Run a quick MSP health check | |MSPDebug.stressScenario('scenario')` ...

(QB_NEW_EN)


[grammar] ~109-~109: There might be a mistake here.
Context: ...)| Run specific stress test scenario | |MSPDebug.testAlerts()` | Test alert s...

(QB_NEW_EN)


[grammar] ~110-~110: There might be a mistake here.
Context: ...ebug.testAlerts()| Test alert system | |MSPDebug.triggerTestAlerts()` | Manua...

(QB_NEW_EN)


[grammar] ~115-~115: There might be a mistake here.
Context: ...Alert Testing | Command | Description | |---------|-------------| | `MSPDebug.se...

(QB_NEW_EN)


[grammar] ~116-~116: There might be a mistake here.
Context: ... Description | |---------|-------------| | MSPDebug.setTestThresholds() | Lower...

(QB_NEW_EN)


[grammar] ~117-~117: There might be a mistake here.
Context: ... | Lower thresholds for easier testing | | MSPDebug.setNormalThresholds() | Res...

(QB_NEW_EN)


[grammar] ~118-~118: There might be a mistake here.
Context: ...esholds()| Restore normal thresholds | |MSPDebug.testAlerts()` | Complete ale...

(QB_NEW_EN)


[grammar] ~123-~123: There might be a mistake here.
Context: ... Visual Tools | Command | Description | |---------|-------------| | `MSPDebug.sh...

(QB_NEW_EN)


[grammar] ~124-~124: There might be a mistake here.
Context: ... Description | |---------|-------------| | MSPDebug.show() | Show visual debug ...

(QB_NEW_EN)


[grammar] ~125-~125: There might be a mistake here.
Context: ....show()| Show visual debug dashboard | |MSPDebug.report()` | Generate and dow...

(QB_NEW_EN)


[grammar] ~142-~142: There might be a mistake here.
Context: ...ation including: - Full error messages - Performance metrics - JSON response data - Test...

(QB_NEW_EN)


[grammar] ~164-~164: There might be a mistake here.
Context: ...e limits with many simultaneous requests 2. rapid-fire - Tests high-frequency requ...

(QB_NEW_EN)


[grammar] ~165-~165: There might be a mistake here.
Context: ... - Tests high-frequency request handling 3. duplicates - Validates duplicate reque...

(QB_NEW_EN)


[grammar] ~166-~166: There might be a mistake here.
Context: ...- Validates duplicate request management 4. timeout-recovery - Tests timeout and r...

(QB_NEW_EN)


[grammar] ~167-~167: There might be a mistake here.
Context: ...y** - Tests timeout and retry mechanisms 5. memory-leaks - Checks for proper clean...

(QB_NEW_EN)


[grammar] ~168-~168: There might be a mistake here.
Context: ...for proper cleanup of completed requests 6. concurrent-mixed - Tests various reque...

(QB_NEW_EN)


[grammar] ~169-~169: There might be a mistake here.
Context: ...sts various request types simultaneously 7. queue-overflow - Tests behavior when q...

(QB_NEW_EN)


[grammar] ~170-~170: There might be a mistake here.
Context: ...sts behavior when queue reaches capacity 8. connection-disruption - Simulates conn...

(QB_NEW_EN)


[grammar] ~171-~171: There might be a mistake here.
Context: ...sruption** - Simulates connection issues 9. performance-load - Tests sustained loa...

(QB_NEW_EN)


[grammar] ~176-~176: There might be a mistake here.
Context: ...quency requests every 10ms for 5 seconds - queue-overflow - Floods queue beyond c...

(QB_NEW_EN)


[grammar] ~177-~177: There might be a mistake here.
Context: ...verflow** - Floods queue beyond capacity - mixed-load - Various request types and...

(QB_NEW_EN)


[grammar] ~188-~188: There might be a mistake here.
Context: ...ze**: Current number of pending requests - Response Times: Average, minimum, and ...

(QB_NEW_EN)


[grammar] ~189-~189: There might be a mistake here.
Context: ...age, minimum, and maximum response times - Success Rate: Percentage of successful...

(QB_NEW_EN)


[grammar] ~190-~190: There might be a mistake here.
Context: ...ate**: Percentage of successful requests - Timeout Rate: Percentage of requests t...

(QB_NEW_EN)


[grammar] ~191-~191: There might be a mistake here.
Context: ...e**: Percentage of requests that timeout - Request Distribution: Breakdown by MSP...

(QB_NEW_EN)


[grammar] ~192-~192: There might be a mistake here.
Context: ...bution**: Breakdown by MSP command codes - Error Tracking: Categorized error type...

(QB_NEW_EN)


[grammar] ~199-~199: There might be a mistake here.
Context: ...Queue Full**: Queue approaching capacity - ⏱️ High Timeout Rate: Excessive requ...

(QB_NEW_EN)


[grammar] ~200-~200: There might be a mistake here.
Context: ...meout Rate**: Excessive request timeouts - 🐌 Slow Responses: Average response ...

(QB_NEW_EN)


[grammar] ~201-~201: There might be a mistake here.
Context: ...ponses**: Average response time too high - 💾 Memory Leak: Callbacks not being ...

(QB_NEW_EN)


[grammar] ~202-~202: There might be a mistake here.
Context: ... Callbacks not being cleaned up properly ## Dashboard Features The visual dashboard...

(QB_NEW_EN)


[grammar] ~208-~208: There might be a mistake here.
Context: ...tatus**: Current queue state and metrics - Live Charts: Queue size and response t...

(QB_NEW_EN)


[grammar] ~209-~209: There might be a mistake here.
Context: ...s**: Queue size and response time trends - Queue Analysis: Detailed breakdown of ...

(QB_NEW_EN)


[grammar] ~210-~210: There might be a mistake here.
Context: ...: Detailed breakdown of pending requests - Alert Display: Active alerts and warni...

(QB_NEW_EN)


[grammar] ~211-~211: There might be a mistake here.
Context: ...rt Display**: Active alerts and warnings - Test Integration: Run tests directly f...

(QB_NEW_EN)


[grammar] ~212-~212: There might be a mistake here.
Context: ...ration**: Run tests directly from the UI - Export Tools: Generate and download re...

(QB_NEW_EN)


[grammar] ~290-~290: There might be a mistake here.
Context: ...## ✅ Current Features #### Alert System - Enhanced debug logging with reduced cons...

(QB_NEW_EN)


[grammar] ~291-~291: There might be a mistake here.
Context: ...debug logging with reduced console noise - Test infrastructure: `triggerTestAlerts(...

(QB_NEW_EN)


[grammar] ~292-~292: There might be a mistake here.
Context: ...erTestAlerts(), setTestThresholds(), setNormalThresholds()` - Visual alert display in dashboard - Smar...

(QB_NEW_EN)


[grammar] ~293-~293: There might be a mistake here.
Context: ...s()` - Visual alert display in dashboard - Smart threshold management for testing ...

(QB_NEW_EN)


[grammar] ~296-~296: There might be a mistake here.
Context: ... for testing #### Interactive Dashboard - Smart update pausing during user interac...

(QB_NEW_EN)


[grammar] ~297-~297: There might be a mistake here.
Context: ... update pausing during user interactions - Clickable test results with detailed inf...

(QB_NEW_EN)


[grammar] ~298-~298: There might be a mistake here.
Context: ...e test results with detailed information - Enhanced interaction handling for all UI...

(QB_NEW_EN)


[grammar] ~299-~299: There might be a mistake here.
Context: ...interaction handling for all UI elements - Visual feedback with updates pause indic...

(QB_NEW_EN)


[grammar] ~302-~302: There might be a mistake here.
Context: ...dates pause indicator #### Complete API - Dual API support: MSPDebug (modern) an...

(QB_NEW_EN)


[grammar] ~303-~303: There might be a mistake here.
Context: ...g(modern) andMSPTestRunner` (legacy) - All documented commands implemented and ...

(QB_NEW_EN)


[grammar] ~304-~304: There might be a mistake here.
Context: ...mented commands implemented and verified - Comprehensive testing methods (9 test ty...

(QB_NEW_EN)


[grammar] ~305-~305: There might be a mistake here.
Context: ...hods (9 test types + 3 stress scenarios) - Real-time monitoring with alert detectio...

(QB_NEW_EN)


[grammar] ~308-~308: There might be a mistake here.
Context: ... alert detection ### ✅ Verified Working - Alert system triggers correctly when thr...

(QB_NEW_EN)


[grammar] ~313-~313: There might be a mistake here.
Context: ...tion as documented - Auto-loading works in development environment ## File Struct...

(QB_NEW_EN)


[grammar] ~333-~333: There might be a mistake here.
Context: ...- Modern browser with ES6 module support - Access to the global MSP object - Cons...

(QB_NEW_EN)


[grammar] ~334-~334: There might be a mistake here.
Context: ...port - Access to the global MSP object - Console access for command-line interfac...

(QB_NEW_EN)


[grammar] ~339-~339: There might be a mistake here.
Context: ...## Troubleshooting Tools not loading: - Ensure MSP object is available globally ...

(QB_NEW_EN)


[grammar] ~343-~343: There might be a mistake here.
Context: ...rt errors Tests failing unexpectedly: - Verify serial connection is active - Che...

(QB_NEW_EN)


[grammar] ~348-~348: There might be a mistake here.
Context: ...ror messages Dashboard not appearing: - Try MSPTestRunner.showDashboard() from...

(QB_NEW_EN)


[grammar] ~349-~349: There might be a mistake here.
Context: ...* - Try MSPTestRunner.showDashboard() from console - Check for CSS conflicts - Ver...

(QB_NEW_EN)


[grammar] ~358-~358: There might be a mistake here.
Context: ...estclass 2. Update monitor metrics inMSPQueueMonitor` 3. Extend dashboard UI as needed 4. Update ...

(QB_NEW_EN)


[grammar] ~364-~364: There might be a mistake here.
Context: ...te this documentation ## License Same as Betaflight Configurator project.

(QB_NEW_EN)


[grammar] ~364-~364: There might be a mistake here.
Context: ...Same as Betaflight Configurator project.

(QB_NEW_EN)

🪛 markdownlint-cli2 (0.17.2)
src/js/msp/debug/MSP_DEBUG_README.md

317-317: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🔇 Additional comments (11)
src/js/msp.js (3)

390-395: Request envelope looks fine.

Capturing start with performance.now() is appropriate for timing instrumentation.


424-426: Sent-callback gating is correct.

Guarding callback_sent on bytesSent === bufferOut.byteLength prevents false positives on partial writes.


1-465: Sanity check complete – no unintended side-effects detected

  • No remaining references to the removed MIN_TIMEOUT or MAX_TIMEOUT fields; all other “timeout” occurrences are unrelated to adaptive timeouts.
  • messageIsJumboFrame is only ever reset in src/js/msp.js (lines 278–281) and is not used elsewhere.
  • Callbacks stored on MSP.callbacks are invoked and cleaned up in src/js/msp/MSPHelper.js (around line 1772), so there’s no missing callback resolution logic in msp.js.

All changes look self-contained; no further action required.

src/js/msp/debug/msp_debug_dashboard.js (3)

883-989: Chart rendering is efficient and Hi-DPI aware.

Nice use of requestAnimationFrame and devicePixelRatio scaling; the <2 points guard prevents div-by-zero in x interpolation.


811-837: Alert text is escaped; good.

Mapping alert keys to user-facing messages while escaping fallback keeps XSS out of innerHTML.


853-879: Queue contents rendering is stable and escaped.

Fixed-slot rendering prevents layout shift; values are escaped; timer presence signal is a nice touch.

src/js/msp/debug/msp_queue_monitor.js (1)

595-617: Ensure safe teardown when other code may have wrapped MSP methods

You restore originals and clear the instrumentation flag—good. If other tooling wraps MSP after the monitor, restoring to our original* could clobber their overrides. If that’s a supported scenario, consider checking identity before restoration or offering a force flag.

Do we ever allow other wrappers on send_message/_dispatch_message concurrently in the Configurator? If yes, I can propose a defensive unwrap sequence.

src/js/msp/debug/msp_test_runner.js (1)

434-438: Global side effect is fine for dev tooling

Exposing window.MSPTestRunner is deliberate here and helps discovery. LGTM.

src/js/msp/debug/msp_stress_test.js (3)

527-549: Report generation composes with monitor report nicely

Solid aggregation. Once the monitor’s Map serialization fix lands, this will produce accurate artifacts.


670-676: Graceful cleanup—good

Stopping the shared monitor without destroying the singleton is the right trade-off here.


247-281: I’ve requested the surrounding context for how TIMEOUT is declared in src/js/msp.js so we can confirm whether it’s a module-level constant or an instance property. Once we have that snippet, we can determine if mutating this.msp.TIMEOUT has any effect and adjust the test accordingly.

haslinghuis and others added 2 commits August 27, 2025 00:47
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link

Copy link
Contributor

Preview URL: https://935e385c.betaflight-configurator.pages.dev

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
src/js/msp/debug/msp_queue_monitor.js (2)

270-284: Convert Maps to plain objects in status/report to make JSON exports accurate.

requestsByCode and errorsByType are Maps; JSON will serialise them as {}. This was previously flagged.

Apply this diff:

@@
-            metrics: { ...this.metrics },
+            metrics: this._toPlainMetrics(),
@@
-            rawMetrics: this.metrics,
+            rawMetrics: this._toPlainMetrics(),

Add this helper inside the class:

_toPlainMetrics() {
    const { requestsByCode, errorsByType, responseTimes, ...rest } = this.metrics;
    return {
        ...rest,
        responseTimes: Array.isArray(responseTimes) ? [...responseTimes] : [],
        requestsByCode: Object.fromEntries(requestsByCode),
        errorsByType: Object.fromEntries(errorsByType),
    };
}

Also applies to: 432-446


256-256: Guard all callbacks accesses to prevent runtime TypeError when MSP is uninitialised or torn down.

There are still direct .length/.map/iteration usages that can throw if this.msp.callbacks is undefined or not an array. Prior feedback already called this out.

Apply this diff:

@@
-        this.currentQueueSize = this.msp.callbacks.length;
+        this.currentQueueSize = this.msp.callbacks?.length ?? 0;
@@
-            currentQueueSize: this.msp.callbacks.length,
+            currentQueueSize: this.msp.callbacks?.length ?? 0,
@@
-            metrics: { ...this.metrics },
+            metrics: this._toPlainMetrics(),
@@
-            queueContents: this.msp.callbacks.map((req) => ({
+            queueContents: (this.msp.callbacks || []).map((req) => ({
                 code: req.code,
                 attempts: req.attempts || 0,
-                age: performance.now() - req.start,
+                age: req?.start ? performance.now() - req.start : 0,
                 hasTimer: !!req.timer,
             })),
@@
-        const callbacks = this.msp.callbacks;
+        const callbacks = this.msp.callbacks || [];

And inside the analysis loop:

@@
-            // Age analysis
-            const age = now - req.start;
+            // Age analysis (guard missing start)
+            const start = typeof req.start === "number" ? req.start : now;
+            const age = now - start;

Also applies to: 273-283, 290-352

🧹 Nitpick comments (8)
src/js/msp/debug/msp_queue_monitor.js (8)

94-114: Make duplicate detection data-type safe (TypedArray/ArrayBuffer/Array) and simpler to read.

The current element-by-element loop assumes data has .length and index access. MSP payloads can be TypedArrays or ArrayBuffers. Centralise equality in a helper to avoid false negatives.

Apply this diff:

-        // Count potential duplicates already present in the queue (same code + same data)
+        // Count potential duplicates already present in the queue (same code + same data)
         try {
-            const callbacks = this.msp.callbacks || [];
-            const isDuplicate = callbacks.some((req) => {
-                if (req.code !== code) return false;
-                const a = data;
-                const b = req.data;
-                if (!a && !b) return true;
-                if (!a || !b) return false;
-                if (a === b) return true;
-                if (a.length !== b.length) return false;
-                for (let i = 0; i < a.length; i++) {
-                    if (a[i] !== b[i]) return false;
-                }
-                return true;
-            });
+            const callbacks = this.msp.callbacks || [];
+            const isDuplicate = callbacks.some((req) => req.code === code && this._dataEquals(data, req.data));
             if (isDuplicate) this.metrics.duplicates++;
         } catch (_) {
             // best-effort only
         }

Add this helper inside the class (e.g., near other private helpers):

// Type-tolerant binary/array equality
_dataEquals(a, b) {
    if (a === b) return true;
    if (!a || !b) return false;

    // ArrayBuffer or views
    const toU8 = (x) => {
        if (x instanceof ArrayBuffer) return new Uint8Array(x);
        if (ArrayBuffer.isView(x)) return new Uint8Array(x.buffer, x.byteOffset, x.byteLength);
        return null;
    };
    const au8 = toU8(a);
    const bu8 = toU8(b);
    if (au8 && bu8) {
        if (au8.byteLength !== bu8.byteLength) return false;
        for (let i = 0; i < au8.byteLength; i++) if (au8[i] !== bu8[i]) return false;
        return true;
    }

    // Arrays
    if (Array.isArray(a) && Array.isArray(b)) {
        if (a.length !== b.length) return false;
        for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
        return true;
    }

    // Fallback for primitives/strings/objects
    try {
        return JSON.stringify(a) === JSON.stringify(b);
    } catch {
        return false;
    }
}

220-233: Emit an immediate snapshot on start to avoid a 1s blind spot.

Apply this diff:

         this.isMonitoring = true;
+        // capture a snapshot immediately
+        this._collectMetrics();
+        this._notifyListeners();
         this.monitoringInterval = setInterval(() => {
             this._collectMetrics();
             this._notifyListeners();
         }, intervalMs);

10-22: Stabilise the metrics shape by initialising successRate and timeoutRate to 0.

These keys are used elsewhere (e.g., grading) but are only populated by _collectMetrics(). Initialising prevents undefined reads before the first interval tick and after resetMetrics().

Apply this diff:

@@
         this.metrics = {
             totalRequests: 0,
             completedRequests: 0,
             failedRequests: 0,
             timeouts: 0,
             duplicates: 0,
             avgResponseTime: 0,
             maxResponseTime: 0,
             queuePeakSize: 0,
             requestsByCode: new Map(),
             responseTimes: [],
             errorsByType: new Map(),
+            successRate: 0,
+            timeoutRate: 0,
         };
@@
     resetMetrics() {
         this.metrics = {
             totalRequests: 0,
             completedRequests: 0,
             failedRequests: 0,
             timeouts: 0,
             duplicates: 0,
             avgResponseTime: 0,
             maxResponseTime: 0,
             queuePeakSize: 0,
             requestsByCode: new Map(),
             responseTimes: [],
             errorsByType: new Map(),
+            successRate: 0,
+            timeoutRate: 0,
         };

Also applies to: 389-401


468-499: Make grading independent of the timer tick by deriving rates locally.

Avoid relying on this.metrics.timeoutRate being refreshed by _collectMetrics().

Apply this diff:

     _calculatePerformanceGrade() {
         let score = 100;
 
-        // Deduct for high timeout rate
-        if (this.metrics.timeoutRate > 0.1) {
+        // Deduct for high timeout rate
+        const timeoutRate =
+            this.metrics.totalRequests > 0 ? this.metrics.timeouts / this.metrics.totalRequests : 0;
+        if (timeoutRate > 0.1) {
             score -= 30;
-        } else if (this.metrics.timeoutRate > 0.05) {
+        } else if (timeoutRate > 0.05) {
             score -= 15;
         }
@@
-        if (this.metrics.avgResponseTime > 2000) {
+        const avg = this.metrics.avgResponseTime || 0;
+        if (avg > 2000) {
             score -= 25;
-        } else if (this.metrics.avgResponseTime > 1000) {
+        } else if (avg > 1000) {
             score -= 10;
         }
@@
-        const failureRate =
-            this.metrics.totalRequests > 0 ? this.metrics.failedRequests / this.metrics.totalRequests : 0;
+        const failureRate =
+            this.metrics.totalRequests > 0 ? this.metrics.failedRequests / this.metrics.totalRequests : 0;

357-369: Return an unsubscribe from addListener for ergonomics.

Apply this diff:

     addListener(callback) {
-        this.listeners.push(callback);
+        this.listeners.push(callback);
+        return () => this.removeListener(callback);
     }

696-698: Avoid instantiating just to destroy; guard the proxy destroy().

Calling mspQueueMonitor.destroy() when no instance exists will currently attempt to construct and then destroy. Early-return instead.

Apply this diff:

-    destroy(...args) {
-        return this.instance.destroy(...args);
-    },
+    destroy(...args) {
+        if (!_mspQueueMonitorInstance) return;
+        return _mspQueueMonitorInstance.destroy(...args);
+    },

179-215: Rename “memory leak” alert or refine detection to avoid false positives.

Queue length spikes indicate backlog, not necessarily leaks. Either rename to “backlog” or track monotonic growth over a window (e.g., > N samples increasing).

If you’d like, I can add a rolling-window slope detector and rename the flag to backlog while keeping a separate memoryLeakSuspected derived from >X minutes of continuous growth.


217-233: Minor: rate/average recomputation could be incremental and cheaper.

The rolling average currently reduces the entire window on each completion. For a 100-sample cap this is fine; if you increase it later, consider keeping a running sum.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3433db1 and 8526b78.

📒 Files selected for processing (1)
  • src/js/msp/debug/msp_queue_monitor.js (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/js/msp/debug/msp_queue_monitor.js (2)
src/js/msp/debug/msp_test_runner.js (6)
  • a (197-197)
  • responseTime (241-241)
  • analysis (166-166)
  • status (145-145)
  • status (245-245)
  • status (375-375)
src/js/msp/MSPHelper.js (2)
  • now (2343-2343)
  • callback (1771-1771)
🔇 Additional comments (2)
src/js/msp/debug/msp_queue_monitor.js (2)

55-82: Instrumentation is correctly bound and guarded against double-patching.

Using bound originals and an instrumentation flag avoids context loss and re-entry issues. Looks good.


640-654: Proxy initialisation pattern is sound.

Lazy initialisation via window.MSP with an informative error is appropriate for the debug-only tooling.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (2)
src/js/msp/debug/msp_stress_test.js (2)

666-673: Fix: runSpecificTest hardcodes "PASSED"; derive status from the actual result

This still reports "PASSED" regardless of failures returned by the test method. Reuse the status derivation helper.

Apply this diff:

         try {
             const result = await testMethod();
             return {
                 name: testName,
-                status: "PASSED",
+                status: this.getTestStatus(testName, result),
                 result,
                 metrics: this.monitor.getStatus(),
             };

393-442: Connection Disruption Test Must Toggle Module-Level Serial Instance

The stress-test’s testConnectionDisruption currently flips this.msp.serial.connected, but msp.send_message()/promise() checks the singleton serial imported in msp.js (import { serial } from "./serial.js"). Mutating this.msp.serial has no effect on the branch in send_message, so the “disconnected” path is never exercised.

• File: src/js/msp/debug/msp_stress_test.js
• Method: testConnectionDisruption

Apply these changes:

  1. Import the real serial singleton from serial.js
  2. Replace all this.msp.serial references with moduleSerial
--- a/src/js/msp/debug/msp_stress_test.js
+++ b/src/js/msp/debug/msp_stress_test.js
@@ 1,5c1,7
-import { mspQueueMonitor } from "./msp_queue_monitor.js";
+import { mspQueueMonitor } from "./msp_queue_monitor.js";
+// Use the same serial instance that MSP methods reference
+import { serial as moduleSerial } from "../../serial.js";

 export class MSPStressTest {
@@ 393,7c395,7
-        const originalConnected = this.msp.serial?.connected;
+        const originalConnected = moduleSerial.connected;
 
         try {
-            if (this.msp.serial) {
-                this.msp.serial.connected = false;
+            moduleSerial.connected = false;
             }
 
             // Try to send requests while "disconnected"
@@ 414,7c416,7
-            if (this.msp.serial) {
-                this.msp.serial.connected = originalConnected;
+            moduleSerial.connected = originalConnected;
             }
 
             // Test recovery
@@ 422,7c424,7
-        } finally {
-            // Ensure connection is restored
-            if (this.msp.serial) {
-                this.msp.serial.connected = originalConnected;
+        } finally {
+            // Ensure connection is restored even on error
+            moduleSerial.connected = originalConnected;
         }

This ensures you’re toggling the exact serial.connected flag that send_message checks.

🧹 Nitpick comments (4)
src/js/msp/debug/msp_stress_test.js (4)

248-260: Duplicate test metrics: distinguish duplicate rejections from other failures

Counting every rejection as a "duplicate" can mask unrelated errors. Separate them so the status helper can be precise.

-        const duplicateErrors = results.filter(
-            (r) => r.status === "rejected" || (r.value && r.value.error && r.value.error.includes("duplicate")),
-        ).length;
+        const isDupErr = (val) => typeof val?.error === "string" && /duplicat(e|ion)/i.test(val.error);
+        const duplicateErrors = results.filter(
+            (r) => r.status === "rejected" || isDupErr(r.value)
+        ).length;
+        const otherFailures = results.filter(
+            (r) => r.status === "rejected" || (!!r.value && !!r.value.error)
+        ).length - duplicateErrors;
 
         return {
             duplicatesSent: duplicateCount,
             successful,
             duplicateRejections: duplicateErrors,
+            otherFailures,
             queueSizeAfter: this.msp.callbacks.length,
         };

268-299: Timeout Recovery: verify the error is actually a timeout before claiming success

Any rejection is treated as a timeout. Tighten detection to avoid false positives and improve diagnostics.

-            try {
-                await this.msp.promise(code, null);
-                return { error: "Expected timeout but request succeeded" };
-            } catch (error) {
+            try {
+                await this.msp.promise(code, null);
+                return { error: "Expected timeout but request succeeded" };
+            } catch (error) {
                 const timeoutTime = performance.now() - startTime;
 
                 // Test that new requests work after timeout
                 this.msp.TIMEOUT = originalTimeout;
                 await this.wait(200);
 
                 const recoveryStart = performance.now();
                 await this.msp.promise(this.testCodes.MSP_IDENT, null);
                 const recoveryTime = performance.now() - recoveryStart;
 
                 return {
-                    timeoutOccurred: true,
+                    timeoutOccurred: /timeout/i.test(error?.message || "") || error?.name === "TimeoutError" || error?.code === "ETIMEDOUT",
                     timeoutDuration: timeoutTime,
                     recoveryTime,
                     queueCleanedUp: this.msp.callbacks.length === 0,
                 };
             }

Optionally add a helper to centralize timeout detection if used elsewhere.


367-388: Queue Overflow: count overflow-specific rejections, not all errors

Right now any rejection increments overflow count, which can conflate unrelated failures. Filter by recognizable overflow messages to reduce false positives.

-        const results = await Promise.allSettled(promises);
-        const rejected = results.filter((r) => r.status === "rejected" || r.value?.error).length;
+        const results = await Promise.allSettled(promises);
+        const isOverflow = (val) =>
+            typeof val?.error === "string" && /(overflow|queue\s*full)/i.test(val.error);
+        const rejectedDueToOverflow = results.filter(
+            (r) => r.status === "rejected" || isOverflow(r.value)
+        ).length;
 
         return {
             attemptedRequests: overflowCount,
             maxQueueSize: maxQueue,
-            rejectedDueToOverflow: rejected,
-            overflowHandled: rejected > 0,
+            rejectedDueToOverflow,
+            overflowHandled: rejectedDueToOverflow > 0,
             finalQueueSize: this.msp.callbacks.length,
         };

545-567: Report: include a guard for empty results to avoid division-by-zero and NaN successRate

Not likely here, but runSpecificTest consumers might call generateTestReport([]). Cheap guard improves resilience.

-        const totalTests = results.length;
+        const totalTests = results.length || 0;
         const passedTests = results.filter((r) => r.status === "PASSED").length;
-        const failedTests = totalTests - passedTests;
+        const failedTests = Math.max(0, totalTests - passedTests);
@@
-            successRate: passedTests / totalTests,
+            successRate: totalTests ? passedTests / totalTests : 0,
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8526b78 and 935e385.

📒 Files selected for processing (1)
  • src/js/msp/debug/msp_stress_test.js (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/js/msp/debug/msp_stress_test.js (2)
src/js/msp/debug/msp_queue_monitor.js (2)
  • mspQueueMonitor (643-713)
  • mspQueueMonitor (643-713)
src/js/msp/debug/msp_test_runner.js (4)
  • result (84-84)
  • result (374-374)
  • results (110-110)
  • report (185-185)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: App
Development

Successfully merging this pull request may close these issues.

3 participants